package com.szm.sentinelx.slots.defaults.flow;

import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.property.DynamicSentinelProperty;
import com.alibaba.csp.sentinel.property.PropertyListener;
import com.alibaba.csp.sentinel.property.SentinelProperty;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleUtil;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.szm.sentinelx.slots.config.SentinelXConfig;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.StampedLock;
import java.util.stream.Collectors;


/**
 * @author: shaozeming
 * @date: 2022/8/11 23:40
 * @description:
 **/
public class DefaultFlowRuleManager {


    private static List<FlowRule> flowRules = new ArrayList<>();

    private static final Map<String, List<FlowRule>> flowRulesBack = new ConcurrentHashMap<>();

    private static final FlowPropertyListener LISTENER = new FlowPropertyListener();
    private static SentinelProperty<List<FlowRule>> currentProperty
            = new DynamicSentinelProperty<>();

    static final StampedLock lock = new StampedLock();

    static {
        currentProperty.addListener(LISTENER);
    }


    public static void register2Property(SentinelProperty<List<FlowRule>> property) {
        AssertUtil.notNull(property, "property cannot be null");
        synchronized (LISTENER) {
            if (currentProperty != null) {
                currentProperty.removeListener(LISTENER);
            }
            property.addListener(LISTENER);
            currentProperty = property;
            RecordLog.info("[DefaultRuleManager] Registering new property to default rule manager");
        }
    }

    /**
     * Get a copy of the rules.
     *
     * @return a new copy of the rules.
     */
    public static List<FlowRule> getRules(String resource) {

        if(SentinelXConfig.isEnableCopy()){
            return getByCopy(resource);
        }
        return flowRules;
    }

    public static List<FlowRule> getByCopy(String resource) {

        if(flowRulesBack.get(resource)!=null ){
            return flowRulesBack.get(resource);
        }

        List<FlowRule> ruleList = new ArrayList<>();
        long stamp = lock.tryReadLock();
        if(stamp<= 0){
            return ruleList;
        }
        try {
            for (FlowRule flowRule: flowRules){
                ruleList.add(FlowRuleCopy.replace(resource,flowRule));
            }
            //必须通过这个方法，否则rater无法设置
            Map<String, List<FlowRule>> ruleMap = FlowRuleUtil.buildFlowRuleMap(ruleList);
            flowRulesBack.put(resource,ruleMap.values().stream()
                    .flatMap(List::stream)
                    .collect(Collectors.toList()));
        }finally {
            lock.unlockRead(stamp);
        }

        return ruleList;
    }




    /**
     * Load {@link FlowRule}s, former rules will be replaced.
     *
     * @param rules new rules to load.
     */
    public static void loadRules(List<FlowRule> rules) {
        currentProperty.updateValue(rules);
    }


    public static Map<String, List<FlowRule>> getBackRules() {
        return flowRulesBack;
    }


    private static final class FlowPropertyListener implements PropertyListener<List<FlowRule>> {

        @Override
        public void configUpdate(List<FlowRule> value) {
            loadConfig(value);
            RecordLog.info("[DefaultFlowRuleManager] Flow rules received: " + flowRules);
        }

        @Override
        public void configLoad(List<FlowRule> conf) {
            loadConfig(conf);
            RecordLog.info("[DefaultFlowRuleManager] Flow rules loaded: " + flowRules);
        }

        private void loadConfig(List<FlowRule> conf){
            long stamp = lock.writeLock();
            try {
                Map<String, List<FlowRule>> rules = FlowRuleUtil.buildFlowRuleMap(conf);
                if (rules == null) {
                    flowRules.clear();
                    flowRulesBack.clear();
                    return;
                }

                flowRules.clear();
                for (List<FlowRule> flowRuleList : rules.values()) {
                    flowRules.addAll(flowRuleList);
                }

                List<FlowRule> defaultRuleList = new ArrayList<>();
                for (String name : flowRulesBack.keySet()) {
                    for (FlowRule flowRule : flowRules) {
                        defaultRuleList.add(FlowRuleCopy.replace(name, flowRule));
                    }
                }
                Map<String, List<FlowRule>> defaultFlowMap = FlowRuleUtil.buildFlowRuleMap(defaultRuleList);
                flowRulesBack.clear();
                flowRulesBack.putAll(defaultFlowMap);
            }finally {
                lock.unlockWrite(stamp);
            }
        }
    }


}
